//License
/***
 * Java TelnetD library (embeddable telnet daemon)
 * Copyright (c) 2000-2005 Dieter Wimberger 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the author nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *  
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 ***/
package telnetd.io.toolkit;

import java.util.logging.Level;
import java.util.logging.Logger;
import telnetd.io.BasicTerminalIO;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Vector;
import telnetd.io.TerminalIO;

/**
 * Class that implements an Editarea.
 *
 * @author Dieter Wimberger
 * @version 2.0 (16/07/2006)
 */
public class Editarea
        extends ActiveComponent {

    //Members
    private int m_ColCursor = 0;
    private int m_RowCursor = 0;
    private int m_Rows = 0;
    private boolean m_Firstrun = true;
    private int m_FirstVisibleRow = 0;
    private int m_LastCursor = 0;
    private String m_Hardwrap = "\n";
    private String m_Softwrap = " ";
    //Associations
    private Vector lines;
    private Editline line;

    public Editarea(BasicTerminalIO io, String name, int rowheight, int maxrows) {
        super(io, name);
        lines = new Vector();
        m_Rows = maxrows;
        m_Firstrun = true;
        m_FirstVisibleRow = 0;
        setDimension(new Dimension(m_IO.getColumns(), rowheight));
    }//constructor

    /**
     * Accessor method for field buffer size.
     *
     * @return int that represents the number of chars in the fields buffer.
     */
    public int getSize() {
        int size = 0;
        //iterate over buffers and accumulate size
        //think of solution for hardwraps
        return size;
    }//getSize

    public void setHardwrapString(String str) {
        m_Hardwrap = str;
    }//setHardwrapString

    public String getHardwrapString() {
        return m_Hardwrap;
    }//getHardwrapString

    public void setSoftwrapString(String str) {
        m_Softwrap = str;
    }//setSoftwrapString

    public String getSoftwrapString() {
        return m_Softwrap;
    }//getSoftwrapString

    public String getValue() {
        StringBuffer sbuf = new StringBuffer();
        //iterate over buffers and accumulate size
        Editline el = null;
        for (int i = 0; i < lines.size(); i++) {
            el = getLine(i);
            sbuf.append(el.getValue()).append(((el.isHardwrapped()) ? m_Hardwrap : m_Softwrap));
        }
        return sbuf.toString();
    }//getValue

    /**
     *
     * @param textLines text lines without \n
     */
    public void setValue(ArrayList<String> textLines) {

        //buffers
        lines.removeAllElements();
        //cursor
        m_RowCursor = 0;
        m_ColCursor = 0;


        boolean firstRun = true;

        for (String textLine : textLines) {
            try {


                if (firstRun) {
                    firstRun = false;
                    line = createLine();
                    appendLine(line);
                    
                } else {
                    String wrap = line.getHardwrap();
                    line.setHardwrapped(true);
                    line.append(wrap);
                    appendNewLine();
                    m_RowCursor++;
                }

                //activate new row
                activateLine(m_RowCursor);
                //set value of new row
                line.setValue(textLine);
                line.setCursorPosition(0);
                m_IO.moveLeft(line.size());
            } catch (BufferOverflowException ex) {
                Logger.getLogger(Editarea.class.getName()).log(Level.SEVERE, null, ex);
            } // think of a buffer filling strategy
            catch (IndexOutOfBoundsException ex) {
                Logger.getLogger(Editarea.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(Editarea.class.getName()).log(Level.SEVERE, null, ex);
            }


        }


        // think of a buffer filling strategy

    }//setValue

    public void clear() throws IOException {

        //Buffers
        lines.removeAllElements();
        //Cursor
        m_RowCursor = 0;
        m_ColCursor = 0;
        //Screen
        draw();

    }//clear

    public void run() throws IOException {
        int oldcursor = 0;
        boolean done = false;
        m_IO.setAutoflushing(false);
        //check flag
        if (m_Firstrun) {
            //reset flag
            m_Firstrun = false;
            //make a new editline
            line = createLine();
            appendLine(line);
        }

        do {
            //switch return of a line
            switch (line.run()) {
                case BasicTerminalIO.UP:
                    if (m_RowCursor > 0) {
                        if (m_FirstVisibleRow == m_RowCursor) {
                            scrollUp();
                        } else {
                            cursorUp();
                        }
                    } else {
                        m_IO.bell();
                    }
                    break;
                case BasicTerminalIO.DOWN:

                    if (m_RowCursor < (lines.size() - 1)) {
                        if (m_RowCursor == m_FirstVisibleRow + (m_Dim.getHeight() - 1)) {
                            scrollDown();
                        } else {
                            cursorDown();
                        }
                    } else {
                        m_IO.bell();
                    }
                    break;
                case BasicTerminalIO.ENTER:
                    /*
                    System.out.println("DEBUG:firstVisibleRow:"+firstVisibleRow);
                    System.out.println("DEBUG:rowCursor:"+rowCursor);
                    System.out.println("DEBUG:lines:"+lines.size());
                    System.out.println("DEBUG:maxRows:"+myRows);
                    System.out.println("DEBUG:height:"+myDim.getHeight());
                     */
                    //ensure exit on maxrows line
                    if (m_RowCursor == (m_Rows - 1)) {
                        done = true;
                    } else {
                        if (!hasLineSpace()) {
                            m_IO.bell();
                        } else {
                            String wrap = line.getHardwrap();
                            line.setHardwrapped(true);

                            if (m_RowCursor == (lines.size() - 1)) {
                                appendNewLine();
                            } else {
                                insertNewLine();
                            }
                            //cursor
                            m_RowCursor++;
                            //activate new row
                            activateLine(m_RowCursor);
                            //set value of new row
                            try {
                                line.setValue(wrap);
                                line.setCursorPosition(0);
                                m_IO.moveLeft(line.size());
                            } catch (Exception ex) {
                            }
                        }
                    }
                    break;
                case TerminalIO.ESCAPE:
                    //set cursor to end of field?

                    done = true;
                    break;

                case BasicTerminalIO.LEFT:
                    if (m_RowCursor > 0) {
                        if (m_FirstVisibleRow == m_RowCursor) {
                            scrollUp();
                            line.setCursorPosition(line.size());
                            m_IO.moveRight(line.size());
                        } else {
                            //Cursor
                            m_RowCursor--;
                            //buffer
                            activateLine(m_RowCursor);
                            line.setCursorPosition(line.size());

                            //screen
                            m_IO.moveUp(1);
                            m_IO.moveRight(line.size());
                        }
                    } else {
                        m_IO.bell();
                    }
                    break;
                case BasicTerminalIO.RIGHT:
                    if (m_RowCursor < (lines.size() - 1)) {
                        if (m_RowCursor == m_FirstVisibleRow + (m_Dim.getHeight() - 1)) {
                            line.setCursorPosition(0);
                            m_IO.moveLeft(line.size());
                            scrollDown();
                        } else {
                            //Cursor
                            m_RowCursor++;
                            //screen horizontal
                            m_IO.moveLeft(line.size());
                            //buffer
                            activateLine(m_RowCursor);
                            line.setCursorPosition(0);
                            //screen
                            m_IO.moveDown(1);
                        }
                    } else {
                        m_IO.bell();
                    }
                    break;
                case BasicTerminalIO.BACKSPACE:
                    if (m_RowCursor == 0 || line.size() != 0 || m_RowCursor == m_FirstVisibleRow) {
                        m_IO.bell();
                    } else {
                        //take line from buffer
                        //and draw update all below
                        removeLine();
                    }
                    break;
                default:
                    if (!hasLineSpace()) {
                        m_IO.bell();
                    } else {
                        String wrap = line.getSoftwrap();
                        //System.out.println("softwrap:"+wrap);
                        line.setHardwrapped(false);

                        if (m_RowCursor == (lines.size() - 1)) {
                            appendNewLine();
                        } else {
                            insertNewLine();
                        }
                        //cursor
                        m_RowCursor++;
                        //activate new row
                        activateLine(m_RowCursor);
                        //set value of new row
                        try {
                            line.setValue(wrap);
                            //getLine(rowCursor-1).getLastRelPos();
                            //line.setCursorPosition(0);
                            //myIO.moveLeft(line.size());
                        } catch (Exception ex) {
                        }
                    }

            }
            m_IO.flush();
        } while (!done);
    }//run

    private void scrollUp() throws IOException {

        int horizontalpos = line.getCursorPosition();
        //System.out.println("Debug:scrolling:up:horpos:"+horizontalpos);
        //System.out.println("Debug:scrolling:up");
        //Cursors
        m_FirstVisibleRow--;
        m_RowCursor--;

        //buffer
        activateLine(m_RowCursor);
        line.setCursorPosition(horizontalpos);

        //screen
        //horizontal

        //content:
        int lasthorizontal = horizontalpos;
        int count = 0;
        for (int i = m_FirstVisibleRow; i < (m_FirstVisibleRow + m_Dim.getHeight()) && i < lines.size(); i++) {
            //System.out.println("Debug:scrolling:up:drawing line "+i);
            m_IO.moveLeft(lasthorizontal);
            Editline lin = (Editline) lines.elementAt(i);
            lasthorizontal = lin.size();
            m_IO.eraseToEndOfLine();
            m_IO.write(lin.getValue());
            m_IO.moveDown(1);
            count++;
        }
        //vertical:
        m_IO.moveUp(count);
        //horizontal:
        if (lasthorizontal > horizontalpos) {
            m_IO.moveLeft(lasthorizontal - horizontalpos);
        } else if (lasthorizontal < horizontalpos) {
            m_IO.moveRight(horizontalpos - lasthorizontal);
        }

        if (horizontalpos > line.getCursorPosition()) {
            m_IO.moveLeft(horizontalpos - line.getCursorPosition());
        }
    }//scrollUp

    private void cursorUp() throws IOException {
        //System.out.println("Debug:cursor:up");

        int horizontalpos = line.getCursorPosition();
        //Cursor
        m_RowCursor--;
        //buffer
        activateLine(m_RowCursor);
        line.setCursorPosition(horizontalpos);
        //screen
        //vertical
        m_IO.moveUp(1);
        //horizontal
        if (horizontalpos > line.getCursorPosition()) {
            m_IO.moveLeft(horizontalpos - line.getCursorPosition());
        }
    }//cursorUp

    private void scrollDown() throws IOException {
        //System.out.println("Debug:scrolling:down");
        int horizontalpos = line.getCursorPosition();

        //Cursors
        m_FirstVisibleRow++;
        m_RowCursor++;

        //buffer
        activateLine(m_RowCursor);
        line.setCursorPosition(horizontalpos);

        //screen
        //vertical:
        m_IO.moveUp(m_Dim.getHeight() - 1);
        //content:
        int lasthorizontal = horizontalpos;
        for (int i = m_FirstVisibleRow; i < (m_FirstVisibleRow + m_Dim.getHeight()); i++) {
            //System.out.println("Debug:scrolling:up:drawing line "+i);
            m_IO.moveLeft(lasthorizontal);
            Editline lin = (Editline) lines.elementAt(i);
            lasthorizontal = lin.size();

            m_IO.eraseToEndOfLine();
            m_IO.write(lin.getValue());
            m_IO.moveDown(1);
        }
        //correct move down and last write
        m_IO.moveUp(1);
        //horizontal:
        if (lasthorizontal > horizontalpos) {
            m_IO.moveLeft(lasthorizontal - horizontalpos);
        } else if (lasthorizontal < horizontalpos) {
            m_IO.moveRight(horizontalpos - lasthorizontal);
        }

        if (horizontalpos > line.getCursorPosition()) {
            m_IO.moveLeft(horizontalpos - line.getCursorPosition());
        }
    }//scrollDown

    private void cursorDown() throws IOException {
        //System.out.println("Debug:cursor:down");
        int horizontalpos = line.getCursorPosition();
        //Cursor
        m_RowCursor++;
        //buffer
        activateLine(m_RowCursor);
        line.setCursorPosition(horizontalpos);
        //screen
        m_IO.moveDown(1);
        if (horizontalpos > line.getCursorPosition()) {
            m_IO.moveLeft(horizontalpos - line.getCursorPosition());
        }
    }//cursorDown

    private void appendNewLine() throws IOException {
        //System.out.println("Debug:appendline");
        //buffer
        appendLine(createLine());

        if (m_RowCursor == m_FirstVisibleRow + (m_Dim.getHeight() - 1)) {
            //System.out.println("Debug:appendline:scroll");
            //this will "scroll"
            m_FirstVisibleRow++;
            //System.out.println("Debug:appendline:scroll:firstvis:"+firstVisibleRow);
            //System.out.println("Debug:appendline:scroll:rowCursor:"+rowCursor);
            //System.out.println("Debug:appendline:scroll:movevert:"+(myDim.getHeight()-1));
            //vertical
            m_IO.moveUp(m_Dim.getHeight() - 1);
            m_IO.moveLeft(line.getCursorPosition());
            //content
            int lasthorizontal = line.getCursorPosition();
            for (int i = m_FirstVisibleRow; i < (m_FirstVisibleRow + m_Dim.getHeight()); i++) {
                //System.out.println("Debug:appendline:scroll:line:"+i);
                Editline lin = (Editline) lines.elementAt(i);
                m_IO.eraseToEndOfLine();
                m_IO.write(lin.getValue());
                m_IO.moveLeft(lin.size());
                m_IO.moveDown(1);
            }
            //correct the move to down in last place
            m_IO.moveUp(1);

        } else {
            //System.out.println("Debug:appendline:NOscroll");
            //this wont need a scroll redraw
            m_IO.moveLeft(line.getCursorPosition());
            m_IO.moveDown(1);
        }
    }//appendNewLine

    private void insertNewLine() throws IOException {
        //System.out.println("Debug:insertline:");
        //buffer
        insertLine(m_RowCursor + 1, createLine());


        if (m_RowCursor == m_FirstVisibleRow + (m_Dim.getHeight() - 1)) {
            //System.out.println("Debug:insertline:scroll");
            //this will "scroll"
            m_FirstVisibleRow++;
            //System.out.println("Debug:insertline:scroll:firstvis:"+firstVisibleRow);
            //System.out.println("Debug:appendline:scroll:rowCursor:"+rowCursor);
            //System.out.println("Debug:appendline:scroll:movevert:"+(myDim.getHeight()-1));
            //vertical
            m_IO.moveUp(m_Dim.getHeight() - 1);
            //content
            int lasthorizontal = line.getCursorPosition();
            for (int i = m_FirstVisibleRow; i < (m_FirstVisibleRow + m_Dim.getHeight()); i++) {
                //System.out.println("Debug:appendline:scroll:line:"+i);
                m_IO.moveLeft(lasthorizontal);
                Editline lin = (Editline) lines.elementAt(i);
                lasthorizontal = lin.size();
                m_IO.eraseToEndOfLine();
                m_IO.write(lin.getValue());
                m_IO.moveDown(1);

            }
            //correct the move to down in last place
            m_IO.moveUp(1);

        } else {
            //System.out.println("Debug:insertline:NOscroll");
            //we have to redraw any line below rowCursor+1 anyway
            m_IO.moveDown(1);
            m_IO.moveLeft(line.getCursorPosition());

            int count = 0;
            for (int i = m_RowCursor + 1; i < (m_FirstVisibleRow + m_Dim.getHeight()) && i < lines.size(); i++) {
                //System.out.println("Debug:insertline:redrawing line:"+i);
                m_IO.eraseToEndOfLine();
                Editline lin = (Editline) lines.elementAt(i);
                m_IO.write(lin.getValue());
                m_IO.moveLeft(lin.size());
                m_IO.moveDown(1);
                count++;
            }
            m_IO.moveUp(count);
        }

    }//insertNewLine

    private void removeLine() throws IOException {

        //buffer
        deleteLine(m_RowCursor);
        activateLine(m_RowCursor - 1);
        //Cursor
        m_RowCursor--;


        //Screen
        //content redraw
        int lasthorizontal = 0;
        int count = 0;
        for (int i = m_RowCursor + 1; i < (m_FirstVisibleRow + m_Dim.getHeight()); i++) {
            if (i < lines.size()) {
                //System.out.println("Debug:removeline:redrawing line:"+i);
                m_IO.eraseToEndOfLine();
                Editline lin = (Editline) lines.elementAt(i);
                m_IO.write(lin.getValue());
                m_IO.moveLeft(lin.size());
                m_IO.moveDown(1);
                count++;
            } else {
                m_IO.eraseToEndOfLine();
                m_IO.moveDown(1);
                count++;
            }
        }
        //cursor readjustment
        //vertical
        m_IO.moveUp(count + 1);
        //horizontal

        line.setCursorPosition(line.size());
        m_IO.moveRight(line.size());
    }//removeLine

    public void draw() throws IOException {
        if (m_Position != null) {
            m_IO.setCursor(m_Position.getRow(), m_Position.getColumn());
            int count = 0;
            for (int i = m_FirstVisibleRow; i < (m_FirstVisibleRow + m_Dim.getHeight()) && i < lines.size(); i++) {
                m_IO.eraseToEndOfLine();
                Editline lin = (Editline) lines.elementAt(i);
                m_IO.write(lin.getValue());
                m_IO.moveLeft(lin.size());
                m_IO.moveDown(1);
                count++;
            }
            int corr = (m_FirstVisibleRow + count) - m_RowCursor;
            if (corr > 0) {
                m_IO.moveUp(corr);
            }
        }
        m_IO.flush();
    }//draw

    private void activateLine(int pos) {
        line = getLine(pos);
    }//activateLine

    private boolean hasLineSpace() {
        return (lines.size() < m_Rows);
    }//hasLineSpace

    private Editline createLine() {
        return new Editline(m_IO);
    }//newLine

    private void deleteLine(int pos) {
        lines.removeElementAt(pos);
    }//deleteLine

    private void insertLine(int pos, Editline el) {
        lines.insertElementAt(el, pos);
    }//insertLine

    private void appendLine(Editline el) {
        lines.addElement(el);
    }//appendLine

    private Editline getLine(int pos) {
        return (Editline) lines.elementAt(pos);
    }//getLine
}//class Editarea

